package com.biye.api.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.base.common.edu.entity.Department;
import com.base.common.edu.entity.EduClass;
import com.base.common.edu.entity.Staff;
import com.base.common.edu.entity.Student;
import com.base.common.edu.mapper.*;
import com.base.common.edu.util.Dateutil;
import com.base.common.edu.util.FileUtil;
import com.base.common.httpStatusCode.enumDefine.StatusCode;
import com.biye.api.service.ExcelImportService;
import com.biye.api.tool.ExcelTemplate.ExcelImport;
import com.biye.api.tool.ExcelTemplate.StuInfoImportThread;
import com.biye.api.tool.ExcelTemplate.TeaInfoImportThreadNew;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

@Service
@Transactional
public class ExcelImportServiceImpl implements ExcelImportService {

    private DepartmentMapper departmentMapper;

    private ClassMapper classMapper;

    private StudentMapper studentMapper;

    private StaffMapper staffMapper;

    private StuContactMapper stuContactMapper;

    private RoleMapper roleMapper;

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Override
    public JSONObject importStuAndTeaInfo(MultipartFile uploadFile, HttpServletRequest request) {
        JSONObject json = new JSONObject();
        int statusCode = -1;
        String msg = "文件上传失败,请稍后再试";
        if (uploadFile.isEmpty()) {
            msg = "信息导入失败！未上传文件";
            json.put("statusCode", statusCode);
            json.put("msg", msg);
            return json;
        }
        // 判断资源路径是否存在，不存在就创建，即在当前工程目录下创建tmp文件夹
        String tmp_fullPath = FileUtil.getRealPathOfProject() + File.separator + "tmp";
        FileUtil.createFileOrPath( tmp_fullPath );
        // 获取文件全名
        String fileOldName = uploadFile.getOriginalFilename();
        // 获取文件名前缀
        String filePrefix = FileUtil.getFileNameExcludingEnds(fileOldName);
        // 获取文件后缀
        String fileSuffix = FileUtil.getFileNameEnds(fileOldName);
        // 文件重命名，追加字符: 文件全名_时间.扩展名
        String newFileName = filePrefix + "_" + Dateutil.getTimeStampString() + fileSuffix;
        // 文件保存路径（文件流，保存路径＋文件名）
        String savePath = tmp_fullPath + File.separator + newFileName;
        boolean success = FileUtil.saveFile(uploadFile, savePath);
        // 如果文件上传成功，下边就开始解析Excel
        if (success){
            File excelImport = new File(savePath);
            Map<String, Object> extract_result = ExcelImport.readStuAndTeaInfoFromExcel(excelImport);
            // 中间对抽取的数据逻辑做处理
            boolean result_is = (boolean) extract_result.get("result");
            if ( result_is ){
                init();
                // 如果为true
                Map<String, String> dept = new HashMap<>();  // name->id
                Map<String, String> clas = new HashMap<>();  // name@grade@no@Department->id
                // 首先把文档里的部门都进行查询，然后返回一个部门和Id的映射Map
                if ( extract_result.containsKey("departments") ){
//                    dept = new HashMap<>();
                    List<Department> departments1 = departmentMapper.queryListOfDeptName();
                    // 将已存在的部门信息存入表
                    for ( Department dep : departments1 )
                        dept.put(dep.getDeptName(), dep.getDeptId());
                    // 将要导入数据的部门信息一并抽取，这些部门信息已经去重
                    List<String> departments = (List<String>) extract_result.get("departments");
                    Department ddd;
                    for( String import_dept : departments ){
                        if ( !dept.containsKey(import_dept) ){
                            // 需要创建部门到数据库
                            ddd = new Department();
                            ddd.setDeptName(import_dept);
                            int i = departmentMapper.saveDept(ddd);
                            if ( i > 0 )
                                dept.put(import_dept, ddd.getDeptId());
                        }
                    }
                }
                // 班级依据以上部门id关联创建，然后也返回一个班级与id的映射
                if ( extract_result.containsKey("classes") ){
                    List<EduClass> eduClasses1 = classMapper.queryClass(new EduClass());
                    // 将已存在的班级信息存入表
                    for ( EduClass class_ss : eduClasses1 ){
                        StringBuffer sb = new StringBuffer();
                        sb.append(class_ss.getClassName());
                        sb.append("@");
                        sb.append(class_ss.getGradeYear());
                        sb.append("@");
                        sb.append(class_ss.getClassNo());
                        sb.append("@");
                        sb.append(class_ss.getDeptObject().getDeptName());
                        clas.put(sb.toString(), class_ss.getClassId());
                    }
                    // 列表中的数据格式如：class_name@grade_year@class_no@department_name
                    List<String> classes = (List<String>) extract_result.get("classes");
//                    System.out.println(classes.size() + " [999999999");
                    for ( String class_info : classes) {
//                        System.out.println(class_info);
                        if ( !clas.containsKey(class_info) ){
                            // 程序走到这时，证明db里不存在这个班级信息。那首先要确认部门是否存在，若部门不存在，则先创建部门
                            String imp_dept_name = class_info.split("@")[3];
                            Department ddd = new Department();;
                            if ( !dept.containsKey(imp_dept_name) ){
                                ddd.setDeptName(imp_dept_name);
                                departmentMapper.saveDept(ddd);
                                // 这里假设一定会插入成功
                                dept.put(imp_dept_name, ddd.getDeptId());
                            }else{
                                ddd.setDeptId(dept.get(imp_dept_name));
                            }
                            // 取出参数
                            String class_name = class_info.split("@")[0];
                            String grade_year = class_info.split("@")[1];
                            String class_no = class_info.split("@")[2];
                            EduClass css = new EduClass();
                            css.setClassName(class_name);
                            css.setGradeYear(grade_year);
                            css.setClassNo(Integer.parseInt(class_no));
                            css.setDeptObject(ddd);
                            int i = classMapper.addClass(css);
                            if ( i > 0 )
                                clas.put(class_info, css.getClassId());
                        }
                    }
                }
                /**
                 * 每个线程插入的数据为1000 <--- 最优效率
                 * 根据数据记录数获得线程总数，但是并发只有10个线程
                 * 根据线程总数对数据进行切割
                 */
                int mod_=1000;
                // 学生数据
                if( extract_result.containsKey("studentlist") ){
                    List<Student> student_list = (List<Student>) extract_result.get("studentlist");
                    int Thread_size=(student_list.size()%mod_==0)?(student_list.size()/mod_):((student_list.size()/mod_)+1);
                    List<List<Student>> lis = new ArrayList<>(Thread_size);
                    for(int i=0;i<Thread_size;i++){ //[a,b) [0,1000)
                        if(i==(Thread_size-1)){
                            if(student_list.size()%mod_>0){
                                lis.add(student_list.subList(i*mod_, i*mod_+student_list.size()%1000));
                            }else{
                                lis.add(student_list.subList(i*mod_, i*mod_+mod_));
                            }
                        }else{
                            lis.add(student_list.subList(i*mod_, i*mod_+mod_));
                        }
                    }
                    // 根据已经分割的数组处理业务: 使用【以前使用过的】线程的两种方法之其一
                    Thread[] ts=new Thread[Thread_size];
                    for(int i=0;i<Thread_size;i++){
                        ts[i]=new Thread(new StuInfoImportThread(lis.get(i), studentMapper ,sqlSessionFactory, clas, stuContactMapper));
                        ts[i].start();
                        try {
                            ts[i].join();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                // 教师数据
                if( extract_result.containsKey("stafflist") ){
                    List<Staff> staff_list = (List<Staff>) extract_result.get("stafflist");
                    int Thread_size=(staff_list.size()%mod_==0)?(staff_list.size()/mod_):((staff_list.size()/mod_)+1);
                    List<List<Staff>> lis = new ArrayList<>(Thread_size);
                    for(int i=0;i<Thread_size;i++){ //[a,b) [0,1000)
                        if(i==(Thread_size-1)){
                            if(staff_list.size()%mod_>0){
                                lis.add(staff_list.subList(i*mod_, i*mod_+staff_list.size()%1000));
                            }else{
                                lis.add(staff_list.subList(i*mod_, i*mod_+mod_));
                            }
                        }else{
                            lis.add(staff_list.subList(i*mod_, i*mod_+mod_));
                        }
                    }
                    // 根据已经分割的数组处理业务: 使用【以前使用过的】线程的两种方法之其二：【<线程池>】
                    // 二.1. Runnable(不带返回参数)
//                    Thread[] ts=new Thread[Thread_size];
//                    ExecutorService executor = Executors.newFixedThreadPool(Thread_size);
//                    for(int i=0;i<Thread_size;i++){
//                        TeaInfoImportThread th = new TeaInfoImportThread(lis.get(i), staffMapper ,sqlSessionFactory, dept);
//                        executor.execute(th);
//                    }
//                    executor.shutdown(); // 执行一次顺序关闭。停止接收外部submit的任务。等待完成任务后进行关闭整个线程池
//                    while(!executor.isTerminated()){
//                        if(executor.isTerminated())
//                            break;
//                    }
                    // 二.2. Callable(带返回参数)
                    List<Future<Object>> results = new ArrayList<>();
                    Future submit;
                    Thread[] ts=new Thread[Thread_size];
                    ExecutorService executor = Executors.newFixedThreadPool(Thread_size);
                    for(int i=0;i<Thread_size;i++){
                        TeaInfoImportThreadNew th = new TeaInfoImportThreadNew(lis.get(i), staffMapper ,sqlSessionFactory, dept);
                        th.setRoleMapper(roleMapper);
                        submit = executor.submit(th);
                        results.add(submit);
                    }
                    executor.shutdown(); // 执行shutdoen指令，不再接受新线程任务，并把已有的任务做完才会真的关闭整个线程
                    while(!executor.isTerminated()){
                        if(executor.isTerminated())
                            break;
                    }
                    /*
                    for (Future<Object> fut : results) {
                        try {
                            System.out.println(new Date() + "::" + fut.get());
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } catch (ExecutionException e) {
                            e.printStackTrace();
                        }
                    }
                     */
                    // End Of "二.2. Callable(带返回参数)"
                }
                statusCode = StatusCode.Success._statusCode;
                msg = "师生信息导入成功";
            }else{
                // 则为false
                msg = (String) extract_result.get("msg");
            }
            // 最后都要删除这个临时文件
            FileUtil.deleteFile(savePath);
        }
        json.put("statusCode", statusCode);
        json.put("msg", msg);
        return json;

    }

    public void init(){
        /**
         * @Author Maple.Chen
         * @Description 之前由于@Transactional注解下，调用注解@Resource注入的Mapper，操作oracle时，每个mapper自动打开一个sqlSession
         *          然后多个线程的sqlSession相互操作，就造成了Oracle死锁，查询死锁及死锁的sql我下边几行会有注释，这个sql只有拥有sysdba权限的用户能查
         * @Date 22:52 2021/4/11
         **/
        /*
select l.session_id sid,
s.serial#,
l.locked_mode,
l.oracle_username,
s.user#,
l.os_user_name,
s.machine,
s.terminal,
a.sql_text,
a.action
from v$sqlarea a, v$session s, v$locked_object l
where l.session_id = s.sid
and s.prev_sql_addr = a.address
order by sid, s.serial#;
         */
        // 整个事务保持同一个Session
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        this.departmentMapper = sqlSession.getMapper(DepartmentMapper.class);
        this.classMapper = sqlSession.getMapper(ClassMapper.class);
        this.studentMapper = sqlSession.getMapper(StudentMapper.class);
        this.staffMapper = sqlSession.getMapper(StaffMapper.class);
        this.stuContactMapper = sqlSession.getMapper(StuContactMapper.class);
        this.roleMapper = sqlSession.getMapper(RoleMapper.class);
    }

}
